package com.ia.neuralNetwork;

public class neuralNetwork {
	
	final static int ACTIVACION_SIGMOID = 1;
	final static int ACTIVACION_TANH = 2;
	final static int NEGATIVO = -1;
	final static int POSITIVO = 1;
	final static int ZERO = 0;
	
    private static int neuronasEntrada;
    private static int neuronasCapaOculta;
    private static int neuronasSalida;
    private static float maximoError;
    private static int iteraciones;
    public static double errorGlobal;
    private static float coficienteAprendizaje;
    private static double[] delta_2;
    private static double[] errorCapa3;
    private static double[] salidaCapa3;
    private static double[] salidaCapa2;
    private static float[] salidaDeseada;
    public  int reconocidos = 0;
    public  int ENTRADA;
    public  int OCULTA;
    public  int SALIDA;
    private static double err = 0;
    private static double err2 = 0;
    private static double biasWeight1 = 0;
    private static double biasWeight2 = 0;
    public Weight weight;
    public Pattern pattern;
    private static int funcionActivacion;
  
    
    neuralNetwork() {
            super();
    }

    public neuralNetwork(int neuronasIn, int neuronasHide, int neuronasOut, String p,int fActivation) {

            super();

            setNeuronasEntrada(neuronasIn);
            setNeuronasCapaOculta(neuronasHide);
            setNeuronasSalida(neuronasOut);
            
            setFuncionActivacion(fActivation);
            
            weight = new Weight();
            pattern = new Pattern(p);

            setErrorGlobal(1.0);

            ENTRADA = getNeuronasEntrada();
            OCULTA = getNeuronasCapaOculta();
            SALIDA = getNeuronasSalida();

            setMaximoError((float) 0.5);
    }
    
    public void setFuncionActivacion(int f) {
    	funcionActivacion = f;
    }
    
    public static int getFuncionActivacion() {
    	return funcionActivacion;
    }
    
    public double getError() {
            return errorGlobal;
    }

    public boolean isfinishedLearning() {
            return (getErrorGlobal() <= 0);
    }

    public void setIteraciones(int itera) {
            iteraciones = itera;
    }

    public int getIteraciones() {
            return iteraciones;
    }

    public int getNeuronasCapaOculta() {
            return neuronasCapaOculta;
    }

    protected int getNeuronasEntrada() {
            return neuronasEntrada;
    }

    protected int getNeuronasSalida() {
            return neuronasSalida;
    }

    static protected void setNeuronasCapaOculta(int nc) {
            neuronasCapaOculta = nc;
    }

    protected void setNeuronasEntrada(int ne) {
            neuronasEntrada = ne;
    }

    protected void setNeuronasSalida(int s) {
            neuronasSalida = s;
    }

    protected void setMaximoError(float e) {
            maximoError = e;
    }

    public double getErrorGlobal() {
            return errorGlobal;
    }

    protected static void setErrorGlobal(double error) {
            errorGlobal = error;
    }

    protected static float getCoficienteAprendizaje() {
            return coficienteAprendizaje;
    }

    public void setCoficienteAprendizaje(float coficiente) {
            coficienteAprendizaje = coficiente;
    }

    static protected double fActivacion(double x) {
    		
    	    double result;
    		if ( getFuncionActivacion() == ACTIVACION_TANH ) {
    			result = (Math.exp(x*2.0)-1.0)/(Math.exp(x*2.0)+1.0);
    		}
    		else {
    			 result = 1 / (1 + Math.exp(-1 * x));
    		}
    		return (result);
    }

    public double derivativeFunction(double d) {
    		
    	if ( getFuncionActivacion() == ACTIVACION_TANH ) {
            return( 1.0-Math.pow(fActivacion(d), 2.0) );
    	} else {
    		return d*(1.0-d);
    	}
    		
    }

    public String forwardPropagation(String modelo) {

            String[] patron = null;

            salidaCapa2 = new double[this.OCULTA];
            salidaCapa3 = new double[this.SALIDA];

            patron = modelo.split(";");

            // entrada capa 1 salida capa 2
            for (int n = 0; n < this.OCULTA; n++) {
                    double sumaCapa1 = 0;
                    for (int i = 0; i < this.ENTRADA; i++) {
                            float entrada_p = Float.parseFloat(patron[i]);
                            sumaCapa1 += entrada_p * weight.getWeightLayer1(i, n);
                    }
                    sumaCapa1 += this.weight.getBiasCapa1();
                    salidaCapa2[n] = fActivacion(sumaCapa1);

            }

            // 'entrada capa 2 salida capa 3
            for (int n = 0; n < this.SALIDA; n++) {
                    double sumaCapa2 = 0;
                    for (int i = 0; i < this.OCULTA; i++) {
                            sumaCapa2 += salidaCapa2[i] *  weight.getWeightLayer2(n, i);
                    }
                    sumaCapa2 += this.weight.getBiasCapa2();
                    salidaCapa3[n] = fActivacion(sumaCapa2);
            }

            String resultadoSalida = "";

            for (int t = 0; t < SALIDA; t++) {
                    double salidaCapa = salidaCapa3[t];
                    if (salidaCapa<0){
                            resultadoSalida=resultadoSalida+"-1";
                    } else
                            if (salidaCapa >= maximoError) {
                                    resultadoSalida = resultadoSalida + "1";
                            } else {
                                    resultadoSalida = resultadoSalida + "0";
                            }
            }


            return resultadoSalida;
    }

    public void entrenaRed() {

            String[] pa = this.pattern.getPatrones();
            String[] patron = null;

            salidaCapa2 = new double[OCULTA];
            salidaCapa3 = new double[SALIDA];
            errorCapa3 = new double[SALIDA];
            salidaDeseada = new float[SALIDA];

            reconocidos = 0;

            iteraciones++;

            for (int p = 0; p < this.pattern.getNumeroPatrones(); p++) {

                    String salidaPatron = "";
                    patron = pa[p].split(";");
                    for (int s = 0; s < SALIDA; s++) {
                            salidaDeseada[s] = Float.parseFloat(patron[ENTRADA + s]);
                            salidaPatron += patron[ENTRADA + s];
                    }

                    //si la salida de la red es igual a la salida deseada
                    if (salidaPatron.equals(forwardPropagation(pa[p]))) {
                            reconocidos++;
                    } else {

                            // System.out.println(pa[p]+"----------------->KO");

                            //return d*(1.0-d);
                            //derivativeFunction
                            for (int d = 0; d < SALIDA; d++) {
                                    /*errorCapa3[d] = (salidaCapa3[d] * (1 - salidaCapa3[d]))
                                                    * (salidaDeseada[d] - salidaCapa3[d]);*/

                                    errorCapa3[d] = derivativeFunction(salidaCapa3[d])
                                                    * (salidaDeseada[d] - salidaCapa3[d]);

                                    err += errorCapa3[d];
                            }

                    /*       double mediaError =  Math.abs(err/SALIDA);
                             double sumaError=0;
                             for (int r = 0; r < SALIDA; r++) {
                                     sumaError +=Math.abs(Math.pow(errorCapa3[r]-mediaError,2));
                             }
                             double errorQ = Math.abs(sumaError/(SALIDA*(SALIDA-1)));
                             err_2 = Math.sqrt(errorQ);
                             */
                            // calculamos error capa oculta
                            delta_2 = new double[OCULTA];

                            for (int i = 0; i < OCULTA; i++) {
                                    double error = 0.0;
                                    for (int j = 0; j < SALIDA; j++) {
                                            error += this.weight.getWeightLayer2(j, i) * errorCapa3[j];
                                    }

                                    //delta_2[i] = salidaCapa2[i] * (1 - salidaCapa2[i]) * error;
                                    delta_2[i] =derivativeFunction(salidaCapa2[i]) * error;

                            }


                            // 'actualiZamos capa 2
                            for (int c = 0; c < SALIDA; c++) {
                                    for (int o = 0; o < OCULTA; o++) {
                                            double w = this.weight.getWeightLayer2(c, o) + (getCoficienteAprendizaje() * salidaCapa2[o] * errorCapa3[c]);
                                            this.weight.setWeightLayer2(c, o, w);
                                    }
                            }

                            // actualiZamos capa 1
                            for (int x = 0; x < OCULTA; x++) {
                                    for (int j = 0; j < ENTRADA; j++) {
                                            float entrada_p = Float.parseFloat(patron[j]);
                                            double w = this.weight.getWeightLayer1(j, x) + (getCoficienteAprendizaje() * entrada_p * delta_2[x]);
                                            this.weight.setWeightLayer1(j, x, w);
                                    }
                                    err2 += delta_2[x];
                            }

                    }

            }

            errorGlobal = ( this.pattern.getNumeroPatrones()- reconocidos);
            errorGlobal = errorGlobal / this.pattern.getNumeroPatrones();

            /*errorGlobal = err_2;

            errorGlobal = Math.abs(errorGlobal);
            //errorGlobal=Math.abs(err);
*/
            biasWeight2 = this.weight.getBiasCapa2()
                            + (err * getCoficienteAprendizaje());
            biasWeight1 = this.weight.getBiasCapa1()
                            + (err2 * getCoficienteAprendizaje());

            this.weight.setBiasCapa1(biasWeight1);
            this.weight.setBiasCapa2(biasWeight2);

            err = err2 = 0;

    }

}